/*
 * SaveRequest.java
 * 
 * Version information
 * 
 * Mar 24, 2006
 * 
 * Copyright (c) AmSoft Systems, 2006
 */
package net.amsoft.iservice.icontact.webapp.struts.action;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.net.InetAddress;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.amsoft.iservice.icontact.hibernate.ContactPage;
import net.amsoft.iservice.icontact.hibernate.Message;
import net.amsoft.iservice.icontact.hibernate.dataObject.RequestData;
import net.amsoft.iservice.icontact.service.IContact;
import net.amsoft.iservice.icontact.util.IContactConstants;
import static net.amsoft.iservice.icontact.util.IContactConstants.*;
import net.amsoft.iservice.icontact.webapp.struts.form.RequestForm;
import net.amsoft.iservice.isso.client.ISSOClient;
import net.amsoft.iservice.util.EmailService;
import net.amsoft.iservice.util.IServiceUtil;
import net.amsoft.iservice.util.MetaData;
import net.amsoft.iservice.util.ResolverClient;
import net.amsoft.iservice.util.exception.IServiceException;

import org.apache.log4j.Logger;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;

import com.janrain.openid.consumer.AuthRequest;
import com.janrain.openid.consumer.Consumer;
import com.janrain.openid.store.MemoryStore;

import static org.apache.struts.action.ActionMessages.GLOBAL_MESSAGE;

/**
 * The SaveRequest action class is used to save the contact request placed by an
 * user. Anybody can use the contact page of the i-name to place a contact
 * request using his/her i-name, email or none (depending upon the contact page
 * settings).
 * 
 * @author rahul
 * 
 */
public class SaveRequest extends BaseAction {
    public static final Logger oLogger = Logger.getLogger(SaveRequest.class);

    private static final long serialVersionUID = -8426663656696891460L;
    private static final String storeKey = "openid.store";
    private static final String sessionKey = "openid.session";

    public ActionForward process(ActionMapping oMapping, ActionForm oForm,
            HttpServletRequest oRequest, HttpServletResponse oResponse)
            throws Exception {
        oLogger.info("process() : entry");
        if (!"save".equals(oRequest.getParameter("step"))) {
            return oMapping.findForward(FWD_CHKLOGIN);
        }
        RequestForm oRequestForm = (RequestForm) oForm;
        Object objCPage = oRequest.getSession(false)
                .getAttribute(SESSION_CPAGE);
        if (objCPage == null) {
            oMapping.findForward(FWD_CHKLOGIN);
        }
        ContactPage oContactPage = (ContactPage) objCPage;
        ActionForward oFwd = null;
        try {
            if (oRequestForm.getSenderIname().length() > 0
                    || oContactPage.getVerificationType() == VER_TYPE_INAME) {
                oLogger.debug("process() : SenderIName: "
                        + oRequestForm.getSenderIname()
                        + ", Verification Type: "
                        + oContactPage.getVerificationType());

                // If the identifier is subscibed to OpenID service,
                // authenticate using that, else use SAML
                String sOpendIdUrl = ResolverClient
                        .getOpenIdServerUri(oRequestForm.getSenderIname());
                if (sOpendIdUrl != null) {
                    oLogger
                            .debug("process() : authenticate  using OpenId Service");
                    oFwd = verifyInameOpenId(oMapping, oRequestForm, oRequest,
                            oContactPage, sOpendIdUrl);
                } else {
                    oLogger.debug("process() : authenticate  using SAML");
                    oFwd = verifyIname(oMapping, oRequestForm, oRequest,
                            oContactPage);
                }
            } else if (oContactPage.getVerificationType() == VER_TYPE_EMAIL
                    || oRequestForm.getSenderEmail().length() > 0) {
                oLogger.debug("process() : SenderIName: "
                        + oRequestForm.getSenderIname()
                        + ", Verification Type: "
                        + oContactPage.getVerificationType());
                oFwd = verifyEmail(oMapping, oRequestForm, oRequest,
                        oContactPage);
            } else {
                oLogger.debug("process() : SenderIName: "
                        + oRequestForm.getSenderIname()
                        + ", Verification Type: "
                        + oContactPage.getVerificationType());
                oFwd = verifyNone(oMapping, oRequestForm, oRequest,
                        oContactPage);
                oRequest.getSession(false).setAttribute(SESSION_CPAGE, null);
            }
            oLogger.info("process() : exit, return " + oFwd);
            return oFwd;
        } catch (Exception e) {
            // TODO set some ActionError
            oRequest.getSession().removeAttribute(SESSION_SYN);
            oLogger.warn("process() : Exception: " + e.getMessage());
            return oMapping.getInputForward();
        }
    }

    /**
     * It verifies the i-name. The i-name is verified by requesting for its
     * authentication and the authentication can only be gauranteed by the
     * ibroker. Hence request for authentication is forwarded to ibroker of the
     * requestin i-name.
     * 
     * @param oMapping
     *            Action Mapping
     * @param oRequestForm
     *            Request data
     * @param oRequest
     *            Current Http request
     * @param oContactPage
     *            Contact page
     * @return Action
     * @throws IServiceException
     */
    private ActionForward verifyIname(ActionMapping oMapping,
            RequestForm oRequestForm, HttpServletRequest oRequest,
            ContactPage oContactPage) throws IServiceException {
        oLogger.info("verifyIname() : entry");
        ActionMessages oMsgs = new ActionErrors();
        String sSenderIname = oRequestForm.getSenderIname();
        if (sSenderIname == null || sSenderIname.trim().length() == 0) {
            oLogger.debug("verifyIname() : Sender IName: " + sSenderIname);
            oMsgs.add(GLOBAL_MESSAGE, new ActionMessage(ERR_REQUIRED, "iname"));
            saveErrors(oRequest, oMsgs);
            return oMapping.getInputForward();
        }

        MetaData oMetaData = ResolverClient.getFinalMetaData(sSenderIname);
        if (oMetaData == null) {
            oLogger.warn("verifyIname() : MetaData: " + oMetaData + ", I-Name "
                    + sSenderIname + " not resolved");
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    IContactConstants.ERR_INAME_UNRESOLVED));
            saveErrors(oRequest, oMsgs);
            return oMapping.getInputForward();
        }
        String sGlobalInumber = oMetaData.getGlobalInumber();
        String sISSOUrl = oMetaData.getISSOUrl();
        String sRelayState = IServiceUtil.generateUniqueID();
        String sProviderID = oMetaData.getProviderID();
        String sProviderIDHash = IServiceUtil.getOneWayEncValue(sProviderID);
        RequestData oRequestData = new RequestData();
        oRequestData.setProviderID(sProviderID);
        oRequestData.setProviderIDHash(sProviderIDHash);
        oRequestData.setIDPMetaDataEP(oMetaData.getServiceEndPoint());
        oRequestData.setGlobalInumber(sGlobalInumber);
        oRequestData.setRelayState(sRelayState);
        oRequestData.debug(null);
        String sAuthRequest = null;
        try {
            String[] authnContext = IContactConstants.RAC_NAMES_LIST.split(",");
            String sComparison = IContactConstants.RAC_COMPARISON_TYPE;
            sAuthRequest = ISSOClient.createSAMLAuthRequest(sSenderIname, "",
                    IContactConstants.getSPXri(oRequest.getServerName()), true,
                    oRequest.getServerName(), false, authnContext, sComparison);
            oLogger.info("verifyIname() : AuthnContext=" + authnContext
                    + " Comparison=" + sComparison);

            String sRedirectURL = ISSOClient.getRedirectRequestURI(sISSOUrl,
                    sAuthRequest, sRelayState);
            oRequest.getSession(false).setAttribute(SESSION_CONTACT_REQ,
                    oRequestForm);
            oRequest.getSession(false).setAttribute(SESSION_REQ_DATA,
                    oRequestData);
            ActionForward oFwd = new ActionForward(sRedirectURL, true);
            oLogger.debug("verifyIname() : exit, Redirecting to " + oFwd);
            return oFwd;
        } catch (Exception e) {
            oLogger.warn("verifyIname() : Exception: " + e.getMessage()
                    + ", throw IServiceException");
            throw new IServiceException();
        }
    }

    /**
     * Verify the contact page when request is through the openid server.
     * 
     * @param oMapping
     *            Action Mapping
     * @param oRequestForm
     *            Request data
     * @param oRequest
     *            Current Http request
     * @param oContactPage
     *            Contact Page
     * @param sOpenIdServerUrl
     *            Url of the openid server
     * @return
     */
    private ActionForward verifyInameOpenId(ActionMapping oMapping,
            RequestForm oRequestForm, HttpServletRequest oRequest,
            ContactPage oContactPage, String sOpenIdServerUrl) {
        oLogger.info("verifyInameOpenId() : entry");
        ActionMessages oMsgs = new ActionErrors();
        String sSenderIname = oRequestForm.getSenderIname();
        String sOpenIdServerUrlBackChannel = null;
        // Check if the memory store exists in the memory
        if (oRequest.getSession().getAttribute(storeKey) == null)
            oRequest.getSession().setAttribute(storeKey, new MemoryStore());

        try {
            URL oUrl = new URL(sOpenIdServerUrl);
            String sIPAddress = InetAddress.getByName(oUrl.getHost())
                    .getHostAddress();
            if (sIPAddress.equals(IContactConstants.LOAD_BALANCER_IP)) {
                sOpenIdServerUrlBackChannel = oUrl.getProtocol()
                        + "://localhost/openid/";
                oLogger.warn("verifyInameOpenId(): changed OpenIdURl="
                        + sOpenIdServerUrlBackChannel);
            } else
                sOpenIdServerUrlBackChannel = sOpenIdServerUrl;
        } catch (Exception e) {
            oRequest.getSession().setAttribute("savedId",
                    escapeAttr(sSenderIname));
            oLogger.error("verifyInameOpenId(): Exception : " + e.getMessage());
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    IContactConstants.ERR_INAME_UNRESOLVED));
            saveErrors(oRequest, oMsgs);
            return oMapping.getInputForward();
        }
        // get a consumer instance
        Consumer c = getConsumer(oRequest);

        // Create an Authrequest object from the submitted value
        AuthRequest ar;
        try {
            ar = c.begin(sSenderIname, sOpenIdServerUrlBackChannel);
            if (ar == null || ar.getAssoc().getRemainingLife() <= 0) {
                // Unknow response from OpenId Server
                throw new IServiceException(2040);
            }
            oRequest.getSession(false).setAttribute(SESSION_CONTACT_REQ,
                    oRequestForm);
        } catch (Exception e) {
            // An IOException indicates a failure, the message will contain some
            // information about it. Typically this means that the user's input
            // wasn't useable as an OpenID url. Handle by displaying the error
            // message and dropping the user back a the starting point

            oRequest.getSession().setAttribute("savedId",
                    escapeAttr(sSenderIname));
            oLogger
                    .error("verifyInameOpenId() : Exception : "
                            + e.getMessage());
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    IContactConstants.ERR_INAME_UNRESOLVED));
            saveErrors(oRequest, oMsgs);
            return oMapping.getInputForward();
        }

        // construct trust root and return to URLs.
        String port = "";
        if (oRequest.getServerPort() != 80) {
            port = ":" + oRequest.getServerPort();
        }

        String sTrustRoot = "http://" + oRequest.getServerName() + port + "/";
        String sContext = "icontact/";
        String returnTo = sTrustRoot + sContext + "AssertionConsumerService";

        // send the user the redirect url to proceed with OpenID authentication
        ar.setRedirectUrl(sOpenIdServerUrl);// Resetting
        String sRedirectURL = ar.redirectUrl(sTrustRoot, returnTo);
        ActionForward oFwd = new ActionForward(sRedirectURL, true);
        oLogger.debug("verifyInameOpenId() : exit, Redirecting to " + oFwd);
        return oFwd;

    }

    /**
     * It verifies the e-mail of the user who is trying to place the contact
     * request. The email is verified by sending an email to the provided email
     * address.
     * 
     * @param oMapping
     *            Action Mapping
     * @param oRequestForm
     *            Request data
     * @param oRequest
     *            Current Http request
     * @param oContactPage
     *            Contact page
     * @return Action
     * @throws IServiceException
     */
    private ActionForward verifyEmail(ActionMapping oMapping,
            RequestForm oRequestForm, HttpServletRequest oRequest,
            ContactPage oContactPage) throws IServiceException {
        oLogger.info("verifyEmail() : entry");
        ActionMessages oMsgs = new ActionErrors();
        String sSenderEmail = oRequestForm.getSenderEmail();
        if (sSenderEmail == null || sSenderEmail.trim().length() == 0) {
            oLogger.debug("verifyEmail() : Sender Email: " + sSenderEmail);
            oMsgs.add(GLOBAL_MESSAGE, new ActionMessage(ERR_REQUIRED, "email"));
            saveErrors(oRequest, oMsgs);
            return oMapping.getInputForward();
        }

        String sKey = IServiceUtil.generateUniqueID();
        String sHash = IServiceUtil.getOneWayEncValue(sKey);
        oRequestForm.setHash(sHash);
        sendVerMail(oRequest, oRequestForm, oContactPage, sKey);
        String sRef = (String) oRequest.getSession(false).getAttribute(
                SESSION_REF);
        IContact.saveMessages(oRequestForm.getSynonym(), sHash, sRef,
                sSenderEmail, oRequestForm.getSenderName(), oRequestForm
                        .getSenderIname(), oRequestForm.getMsg(), PENDING);
        oMsgs.add(GLOBAL_MESSAGE, new ActionMessage(MSG_VER_EMAIL_SENT,
                sSenderEmail));
        saveErrors(oRequest, oMsgs);
        oLogger.info("verifyEmail(): Message saved");
        oRequest.getSession(false).removeAttribute(SESSION_CPAGE);
        oRequest.getSession(false).removeAttribute(SESSION_REF);
        oLogger.info("verifyEmail(): exit");
        return oMapping.findForward(FWD_MSG);
    }

    private ActionForward verifyNone(ActionMapping oMapping,
            RequestForm oRequestForm, HttpServletRequest oRequest,
            ContactPage oContactPage) throws IServiceException {
        oLogger.info("verifyNone() : entry");
        String sRef = (String) oRequest.getSession(false).getAttribute(
                SESSION_REF);

        Message oSavedMsg = IContact.saveMessages(oRequestForm.getSynonym(),
                IServiceUtil.generateUniqueID(), sRef, oRequestForm
                        .getSenderEmail(), oRequestForm.getSenderName(),
                oRequestForm.getSenderIname(), oRequestForm.getMsg(), ACTIVE);

        ActionMessages oMsgs = new ActionErrors();
        oMsgs.add(GLOBAL_MESSAGE, new ActionMessage(MSG_REQ_SENT));
        saveErrors(oRequest.getSession(false), oMsgs);
        oLogger.info("verifyNone(): Request saved");
        sendNotificationEmail(oSavedMsg, oRequest);
        oRequest.getSession(false).removeAttribute(SESSION_CPAGE);
        oRequest.getSession(false).removeAttribute(SESSION_REF);
        oLogger.info("verifyNone() : exit");
        return oMapping.findForward(FWD_MSG);
    }

    /**
     * Sends a verification email containing a verification link.
     * 
     * @param oRequest
     *            Current Http Request
     * @param oRequestForm
     *            Request data
     * @param oContactPage
     *            Contact page
     * @param sKey
     *            Key used to send verification link
     */
    private void sendVerMail(HttpServletRequest oRequest,
            RequestForm oRequestForm, ContactPage oContactPage, String sKey) {
        oLogger.info("sendVerMail() : entry");
        String sVerificationLink = prepareVerLink(oRequest, sKey);
        String sCompleteMsg = prepareVerMsg(oRequest, oContactPage,
                sVerificationLink, oRequestForm);
        String sSubject = oContactPage.getVerificationEmailSub();
        if (sSubject == null || sSubject.trim().length() == 0) {
            sSubject = getResources(oRequest).getMessage(oRequest.getLocale(),
                    MSG_DEF_VER_EMAIL_SUB);
        }
        String sFromAddr = "noreply@" + oRequest.getServerName();
        EmailService oEmailService = new EmailService();
        oEmailService.setBody(sCompleteMsg);
        oEmailService.setSubject(sSubject);
        oEmailService.setFromAddress(sFromAddr);
        oEmailService.setReplyToAddress(sFromAddr);
        ArrayList<String> lRecipient = new ArrayList<String>();
        lRecipient.add(oRequestForm.getSenderEmail());
        oEmailService.setReceipientList(lRecipient);
        oEmailService.sendMail();
        oLogger.info("sendVerMail() : exit");
    }

    /**
     * Prepares the verification link to be sent along verification email
     * message.
     * 
     * @param oRequest
     *            Http request
     * @param key
     *            Key for link
     * @return Verification link
     */
    private String prepareVerLink(HttpServletRequest oRequest, String key) {
        oLogger.info("prepareVerLink() : entry");
        String sVerificationLink = oRequest.getScheme() + "://"
                + oRequest.getServerName();
        if (oRequest.getServerPort() != 80) {
            sVerificationLink += ":" + oRequest.getServerPort();
        }
        try {
            sVerificationLink += VERIFICATION_ACTION + "?"
                    + URLEncoder.encode(key, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            oLogger.warn("prepareVerLink(): Exception: " + e.getMessage());
            sVerificationLink += VERIFICATION_ACTION + "?" + key;
        }
        oLogger.info("prepareVerLink() : exit, return Verification Link: "
                + sVerificationLink);
        return sVerificationLink;
    }

    /**
     * Creates the text body of the verification email.
     * 
     * @param oRequest
     *            Http request
     * @param oContactPage
     *            Contact Page
     * @param verLink
     *            Verification link
     * @param oRequestForm
     *            Request data
     * @return text message body
     */
    private String prepareVerMsg(HttpServletRequest oRequest,
            ContactPage oContactPage, String verLink, RequestForm oRequestForm) {
        oLogger.info("prepareVerMsg() : entry");
        String sCompleteMsg = getResources(oRequest).getMessage(
                oRequest.getLocale(), MSG_VER_EMAIL,
                oRequestForm.getSenderName(), oRequestForm.getSynonym(),
                verLink, oContactPage.getVerificationEmailMsg());
        oLogger.info("prepareVerMsg() : exit, return Complete Message: "
                + sCompleteMsg);
        return sCompleteMsg;

    }

    /**
     * This method gets a <code>com.janrain.openid.consumer.Consumer</code>
     * instance appropriate for use in this servlet.
     */
    private Consumer getConsumer(HttpServletRequest oRequest) {
        oLogger.info("getConsumer() : entry");
        // pull the memory store out of the context
        MemoryStore ms = (MemoryStore) oRequest.getSession().getAttribute(
                storeKey);
        // fetch/create a session <code>Map</code> for the consumer's use
        Map session = (Map) oRequest.getSession().getAttribute(sessionKey);
        if (session == null) {
            session = new HashMap();
            oRequest.getSession().setAttribute(sessionKey, session);
        }
        oLogger.info("getConsumer() : exit");
        return new Consumer(session, ms);
    }

    /**
     * This method escapes characters in a string that can cause problems in
     * HTML
     */
    private String escapeAttr(String sAttrStr) {
        if (sAttrStr == null)
            return "";

        StringBuffer result = new StringBuffer();

        for (int i = 0; i < sAttrStr.length(); i++) {
            char c = sAttrStr.charAt(i);
            if (c == '<') {
                result.append("&lt;");
            } else if (c == '>') {
                result.append("&gt;");
            } else if (c == '&') {
                result.append("&amp;");
            } else if (c == '\"') {
                result.append("&quot;");
            } else if (c == '\'') {
                result.append("&#039;");
            } else if (c == '\\') {
                result.append("&#092;");
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }
}
